package com.start.code.generator.service.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.CharsetUtil;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.start.code.generator.configs.StartCodeTemplateConfig;
import com.start.code.generator.service.StartCodeGeneratorService;
import com.start.code.generator.vo.StartCreateCodeVo;
import com.start.common.exceptions.StartBusException;
import com.start.dataSource.entity.StartGenTableColumn;
import com.start.dataSource.entity.StartSourceDataTable;
import com.start.dataSource.entity.StartSourceGenTable;
import com.start.dataSource.mappers.StartDataTableMapper;
import com.start.dataSource.mappers.StartTableColumnMapper;
import com.start.dataSource.service.StartDataTableService;
import lombok.SneakyThrows;
import org.apache.commons.configuration2.PropertiesConfiguration;
import org.apache.commons.configuration2.builder.fluent.Configurations;
import org.apache.commons.configuration2.ex.ConfigurationException;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.text.WordUtils;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;


@Service
public class CodeGeneratorServiceImpl implements StartCodeGeneratorService {

    @Autowired
    private StartTableColumnMapper startSysTableColumnMapper;
    @Autowired
    private StartDataTableMapper startSysDataTableMapper;
    @Autowired
    private StartCodeTemplateConfig startCodeTemplateConfig;

    @Autowired
    private StartDataTableService dataTableService;
    private final PropertiesConfiguration dbToJavaConfig = getConfig("genrator/dbToJava.properties");
    private final PropertiesConfiguration avueToJavaConfig = getConfig("genrator/avueToJava.properties");

    @Override
    @SneakyThrows
    public byte[] generatorCode(String dataSourcesKey, StartCreateCodeVo codeVo) {
        if (!dataTableService.dbHasTable(dataSourcesKey, codeVo.getTableName())) {
            throw new StartBusException("数据表不存在");
        }
        StartSourceGenTable genTable = new StartSourceGenTable();
        List<StartGenTableColumn> columns = null;
        Optional<StartGenTableColumn> prkColumn = null;
        final StartSourceDataTable sourceDataTable = dataTableService.getTableByTableName(dataSourcesKey, codeVo.getTableName(), true);
        if (sourceDataTable != null) {
            columns = crudOptionToColumn(sourceDataTable.getCrudOption());
            prkColumn = columns.stream().filter(e -> e.getColumnName().equals("id")).findFirst();
        } else {
            columns = startSysTableColumnMapper.queryByTable(dataSourcesKey, codeVo.getTableName());
            prkColumn = columns.stream().filter(e -> e.getColumnKey().equals("PRI")).findFirst();
            columns = dbColumnToOption(columns);
        }
        if (StringUtils.isEmpty(codeVo.getTablePrefix())) {
            codeVo.setTablePrefix("start_");
        }
        String columnOption = JSONArray.toJSONString(columns.stream().map(StartGenTableColumn::getStartColumnOption).collect(Collectors.toList()));
        String className = tableToJava(codeVo.getTableName(), codeVo.getTablePrefix());
        codeVo.setClassName(className);
        codeVo.setClassname(StringUtils.uncapitalize(className));
        codeVo.setTableColumnOption(columnOption);
        codeVo.setColumns(columns);
        prkColumn.ifPresent(codeVo::setPk);
        return createCode(codeVo);
    }

    private List<StartGenTableColumn> crudOptionToColumn(JSONObject crudOption) {
        List<StartGenTableColumn> result = new ArrayList<>();
        final JSONArray column = crudOption.getJSONArray("column");
        for (int i = 0; i < column.size(); i++) {
            JSONObject col = column.getJSONObject(i);
            StartGenTableColumn c = new StartGenTableColumn();
            c.setColumnName(col.getString("prop"));
            c.setColumnComment(col.getString("label"));
            String attrName = columnToJava(c.getColumnName());
            c.setCaseAttrName(attrName);
            c.setLowerAttrName(StringUtils.uncapitalize(attrName));
            c.setAttrName(StringUtils.uncapitalize(attrName));
            c.setAttrType(avueToJavaConfig.getString(col.getString("type"), "unknowType"));
            c.setStartColumnOption(col);
            result.add(c);
        }
        return result;
    }

    private List<StartGenTableColumn> dbColumnToOption(List<StartGenTableColumn> columns) {
        for (StartGenTableColumn column : columns) {
            String attrName = columnToJava(column.getColumnName());
            column.setCaseAttrName(attrName);
            column.setLowerAttrName(StringUtils.uncapitalize(attrName));
            column.setAttrName(StringUtils.uncapitalize(attrName));
            String attrType = dbToJavaConfig.getString(column.getDataType(), "unknowType");
            column.setAttrType(attrType);
            column.createOption();
        }
        return columns;
    }


    @SneakyThrows
    private byte[] createCode(StartCreateCodeVo createVo) {
        Map<String, Object> map = new HashMap<>(16);
        map.put("tableName", createVo.getTableName());
        map.put("pk", createVo.getPk());
        map.put("columnOption", createVo.getTableColumnOption());
        map.put("className", createVo.getCaseClassName());
        map.put("classname", createVo.getLowerClassName());
        map.put("pathName", StringUtils.uncapitalize(createVo.getCaseClassName()));
        map.put("columns", createVo.getColumns().stream().filter(StartGenTableColumn::isAddEntity).collect(Collectors.toList()));
        map.put("datetime", DateUtil.now());
        map.put("comments", createVo.getComments());
        map.put("moduleName", createVo.getModuleName());
        map.put("package", createVo.getPackageName());
        map.put("author", createVo.getAuthor());
        if (createVo.getParentId() == null) {
            createVo.setParentId("");
        }
        map.put("parentId", createVo.getParentId());
        VelocityContext velocityContext = new VelocityContext(map);
        Properties veProperties = new Properties();
        veProperties.put("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
        VelocityEngine ve = new VelocityEngine(veProperties);
        ve.init();
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ZipOutputStream zip = new ZipOutputStream(outputStream);
        List<String> codeTemplates = startCodeTemplateConfig.getCodeTemplates();
        for (String template : codeTemplates) {
            StringWriter sw = new StringWriter();
            Template tpl = ve.getTemplate(template, CharsetUtil.UTF_8);
            tpl.merge(velocityContext, sw);
            ZipEntry zipEntry = new ZipEntry(getFileName(template, createVo.getClassName(), createVo.getPackageName(), createVo.getModuleName()));
            zip.putNextEntry(zipEntry);
            IoUtil.write(zip, StandardCharsets.UTF_8, false, sw.toString());
            zip.closeEntry();
        }
        IoUtil.close(zip);
        return outputStream.toByteArray();
    }


    /**
     * 获取文件名
     */
    private String getFileName(String template, String className, String packageName, String moduleName) {
        String packagePath = "src" + File.separator + "main" + File.separator + "java" + File.separator;
        if (StringUtils.isNotBlank(packageName)) {
            packagePath += packageName.replace(".", File.separator) + File.separator + moduleName + File.separator;
        }
        if (template.contains(StartCodeTemplateConfig.JAVA_ENTITY_VM)) {
            return packagePath + "entity" + File.separator + className + "Entity.java";
        }

        if (template.contains(StartCodeTemplateConfig.JAVA_MAPPER_VM)) {
            return packagePath + "mappers" + File.separator + className + "Mapper.java";
        }

        if (template.contains(StartCodeTemplateConfig.JAVA_SERVICE_VM)) {
            return packagePath + "service" + File.separator + className + "Service.java";
        }

        if (template.contains(StartCodeTemplateConfig.JAVA_SERVICE_IMPL_VM)) {
            return packagePath + "service" + File.separator + "impl" + File.separator + className + "ServiceImpl.java";
        }

        if (template.contains(StartCodeTemplateConfig.JAVA_CONTROLLER_VM)) {
            return packagePath + "controller" + File.separator + className + "Controller.java";
        }

        if (template.contains(StartCodeTemplateConfig.XML_MAPPER_VM)) {
            return "src" + File.separator + "main"
                    + File.separator + "resources" + File.separator + "mappers" + File.separator + moduleName + File.separator + className + "Mapper.xml";
        }


        if (template.contains(StartCodeTemplateConfig.VUE_INDEX_VM)) {
            return "src" + File.separator + "views" + File.separator + moduleName + File.separator + StringUtils.uncapitalize(className) + File.separator + "index.vue";
        }

        if (template.contains(StartCodeTemplateConfig.JS_API_VM)) {
            return "src" + File.separator + "api"
                    + File.separator + moduleName + File.separator + StringUtils.uncapitalize(className) + "Api.js";
        }

        if (template.contains(StartCodeTemplateConfig.JS_Option_VM)) {
            return "src" + File.separator + "option"
                    + File.separator + moduleName + File.separator + StringUtils.uncapitalize(className) + "Option.js";
        }

        throw new RuntimeException("未知文件路径");
    }

    /**
     * 列名转换成Java属性名
     */
    public String columnToJava(String columnName) {
        return WordUtils.capitalizeFully(columnName, '_').replace("_", "");
    }

    /**
     * 表名转换成Java类名
     */
    private String tableToJava(String tableName, String tablePrefix) {
        if (StringUtils.isNotBlank(tablePrefix)) {
            tableName = tableName.replaceFirst(tablePrefix, "");
        }
        return columnToJava(tableName);
    }

    /**
     * 获取配置信息
     */
    private PropertiesConfiguration getConfig(String path) {
        Configurations configurations = new Configurations();
        try {
            return configurations.properties(path);
        } catch (ConfigurationException e) {
            throw new RuntimeException("获取配置文件失败，", e);
        }
    }
}
